/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.sensor.shape;

import nl.cloudfarming.sensor.AlreadyParsedException;
import nl.cloudfarming.sensor.model.PlotDataFileBO;
import nl.cloudfarming.util.filecopier.ShapeFileCopier;
import org.openide.util.Exceptions;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.opengis.feature.simple.SimpleFeature;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import nl.cloudfarming.eventbus.GuiEventKey;
import nl.cloudfarming.sensor.Receiver;
import nl.cloudfarming.sensor.ServiceLoaderReceiver;
import nl.cloudfarming.util.filecopier.Exception.DirectoryCreationException;
import nl.cloudfarming.util.filecopier.Exception.FileMoveException;
import nl.cloudfarming.util.filecopier.Exception.NotADirectoryException;
import nl.cloudfarming.util.filecopier.Exception.NotAFileException;
import java.util.Date;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.LayerEventKey;
import nl.cloudfarming.client.geoviewer.LayerProvider;
import nl.cloudfarming.client.sensor.PlotLayerProvider;
import nl.cloudfarming.client.sensor.SensorDataLayer;
import nl.cloudfarming.client.sensor.SensorService;
import nl.cloudfarming.client.warmup.CloudfarmingWarmup;
import nl.cloudfarming.eventbus.GuiEvent;
import nl.cloudfarming.sensor.FileReadException;
import nl.cloudfarming.sensor.model.impl.SensorServiceImpl;
import nl.cloudfarming.sensor.util.MD5Sum;
import nl.cloudfarming.util.NBPreferencesHolder;
import nl.cloudfarming.util.PreferenceEnum;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openide.util.Lookup;

/**
 *
 * @author Merijn Zengers
 */
//TODO [TV:MZ] Receiver pattern is meant for realtime receiving of sensor data, not for shapefile processing
//TODO [MZ:IV] see above
public class ShapeReceiver extends ServiceLoaderReceiver<SimpleFeature, File> implements Receiver<File>, Runnable {

    ShapeFileCopier fileCopier = new ShapeFileCopier();
    ArrayList<File> shapeList = new ArrayList<File>();
    private SensorService sensorService = new SensorServiceImpl();
    private static Log log = LogFactory.getLog(ShapeReceiver.class);

    @Override
    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean canReceive(File receiverPayload) {
        if (receiverPayload.getName().toLowerCase().endsWith(".shp")) {
            return true;
        }
        return false;
    }


    /*
     * Accepts a File as receiverPayload
     */
    @Override
    public synchronized void receive(File receiverPayload) throws NoSuchAlgorithmException, IOException, FileReadException, AlreadyParsedException, NotADirectoryException, NotAFileException, DirectoryCreationException, FileNotFoundException, FileMoveException {
        shapeList.add(receiverPayload);
    }

    @Override
    public boolean isReceiving() {
        return !shapeList.isEmpty();
    }

    @Override
    public void run() {
        for (File f : shapeList) {
            if (f.getName().toLowerCase().endsWith(".shp")) {
                ShapefileDataStore shpDataStore = null;
                if (f.isFile()) {
                    try {
                        String checkSum = MD5Sum.getHex(MD5Sum.createChecksum(new FileInputStream(f)));
                        PlotDataFileBO plotDataFileBO = new PlotDataFileBO(f.getName(), checkSum, new Date(f.lastModified()), null);
                        if (sensorService.findPlotDataFileByNameChecksum(f.getName(), checkSum) == null) {
                            sensorService.persist(plotDataFileBO);

                            shpDataStore = new ShapefileDataStore(f.toURI().toURL());
                            String typeName = shpDataStore.getTypeNames()[0];
                            // TODO: next call generates 2 exceptions
                            //- sun.misc.ServiceConfigurationError: org.opengis.filter.expression.Function: Provider org.geotools.filter.function.math.FilterFunction_toRadians not found
                            //- sun.misc.ServiceConfigurationError: org.geotools.feature.FeatureTypeFactory: Provider org.geotools.feature.DefaultFeatureTypeFactory not found
                            FeatureSource featureSource = shpDataStore.getFeatureSource(typeName);
                            FeatureCollection result = featureSource.getFeatures();
                            FeatureIterator itertor = result.features();
                            while (itertor.hasNext()) {
                                SimpleFeature feature = (SimpleFeature) itertor.next();
                                dispatchHandling(feature, plotDataFileBO);
                            }
                            itertor.close();
                            //In dispatchandling groups are made. This is done to speed up performance this is the only place where
                            //we can retreive the end of file so we must send the remainder to the handler.
                            sendRemainderToHandlers();
                            // shpDataStore.getFeatureReader(Query.ALL, Transaction.AUTO_COMMIT).close();
                            //FIXME hack to update layers loading of sensor data needs refactoring to simpler architecture
                            for (LayerProvider provider : Lookup.getDefault().lookupAll(LayerProvider.class)) {
                                if (provider instanceof PlotLayerProvider) {
                                    for (Layer layer : ((PlotLayerProvider) provider).getLookup().lookupAll(Layer.class)) {
                                        layer.updateLayer();
                                    }
                                }
                            }

                        } else {
                            StringBuilder sb = new StringBuilder("The shape File: ");
                            sb.append(f.getName());
                            sb.append("Is already loaded");
                            log.info(sb);
                        }
                        StringBuilder sb = new StringBuilder((f).getParent());
                        //Get copy to directory from netbeans propperties
                        NBPreferencesHolder preferencesHolder = new NBPreferencesHolder();
                        String loadString = preferencesHolder.getProperty(CloudfarmingWarmup.class, PreferenceEnum.ARCHIVE);
                        File loadFile = new File(loadString);
                        fileCopier.moveFile(f, loadFile, true);
                    } catch (NotADirectoryException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (NotAFileException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (DirectoryCreationException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (FileMoveException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (NoSuchAlgorithmException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            }
        }
    }

    class UpdatedLayerEvent implements GuiEvent<Layer> {

        private final Layer layer;

        public UpdatedLayerEvent(Layer layer) {
            this.layer = layer;
        }

        @Override
        public GuiEventKey getKey() {
            return LayerEventKey.DATA_CHANGED_EVENT;
        }

        @Override
        public Layer getContent() {
            return this.layer;
        }
    }
}
